package cn.bincker.csustcj.edu;

import cn.bincker.csustcj.edu.entity.*;
import cn.bincker.csustcj.edu.exception.LoginExpireException;
import cn.bincker.csustcj.edu.exception.PlatformException;
import cn.bincker.csustcj.edu.utils.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidParameterException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args){
        var scanner = new Scanner(System.in);
        var account = readAccount(scanner);
        var platform = new DefaultPlatform();
        var dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        var errorLogfile = new File("error.log");
        System.out.println("登录中...");
        platform.login(account.getUsername(), account.getPassword());
        var courses = refreshInformation(platform);
        printUsage();
        var retry = false;
        var retryTimes = 0;
        String[] command = args;
        label:
        while (true){
            if (!retry || retryTimes > 1) {
                if (retry){
                    retry = false;
                    retryTimes = 0;
                    System.out.println("重试超过3次.");
                }
                System.out.print("> ");
                var line = scanner.nextLine();
                if (line.isBlank()) {
                    System.out.println();
                    continue;
                }
                command = line.split(" ");
            }else{
                retryTimes ++;
                retry = false;
            }
            try {
                switch (command[0]) {
                    case "watch-video":
                    case "wv":
                        if (command.length < 2) {
                            watchVideo(platform, courses, 0);
                        } else {
                            var index = Integer.parseInt(command[1]) - 1;
                            var startIndex = command.length > 2 ? Integer.parseInt(command[2]) : -1;
                            if (index < 0 || index > courses.size() - 1)
                                throw new IndexOutOfBoundsException("请输入正确的学科序号。");
                            watchVideo(platform, courses.subList(index, courses.size()), startIndex - 1);
                        }
                        break;
                    case "homework":
                        if (command.length < 2) {
                            doHomework(platform, courses, 0);
                        }else{
                            var index = Integer.parseInt(command[1]) - 1;
                            var startIndex = command.length > 2 ? Integer.parseInt(command[2]) - 1 : 0;
                            if (index < 0 || index > courses.size() - 1)
                                throw new IndexOutOfBoundsException("请输入正确的学科序号。");
                            doHomework(platform, courses.subList(index, courses.size()), startIndex);
                        }
                        break;
                    case "score":
                        if (command.length < 2) throw new InvalidParameterException("请输入学科序号");
                        var index = Integer.parseInt(command[1]) - 1;
                        if (index < 0 || index > courses.size() - 1)
                            throw new IndexOutOfBoundsException("请输入正确的学科序号。");
                        printScore(platform, courses.get(index));
                        break;
                    case "refresh":
                        courses = refreshInformation(platform);
                        break;
                    case "exit":
                    case "quit":
                        break label;
                    default:
                        printUsage();
                        break;
                }
                command = new String[0];
                if (args.length > 0) break;
            }catch (LoginExpireException e){
                System.out.println("重新登录...");
                platform.login(account.getUsername(), account.getPassword());
                retry = true;
            }catch (Throwable e){
                try(var out = new PrintWriter(new FileOutputStream(errorLogfile, true))){
                    out.write("[" + dateFormat.format(new Date()) + "]\n");
                    e.printStackTrace(out);
                    out.write("\n");
                } catch (IOException ignore) {}
                System.out.println("错误：" + e.getMessage());
            }
        }
        platform.exit();
    }

    private static void doHomework(DefaultPlatform platform, List<Course> courses, int i) {
        for (int j = 0; j < courses.size(); j++) {
            Course course = courses.get(j);
            System.out.println("获取平时作业列表[" + course.getName() + "]...");
            var homeworks = platform.getExaminationList(course);
            for (var k = j == 0 ? i : 0; k < homeworks.size(); k++) {
                var homework = homeworks.get(k);
                System.out.println("读取考试记录[" + homework.getTitle() + "]...");
                var records = platform.getExaminationRecord(homework);
                if (records.stream().anyMatch(r->r.getScore().matches("\\d+(\\.\\d+)?") && Double.parseDouble(r.getScore()) > 60)){
                    System.out.println("分数达标，跳过[" + homework.getTitle() + "]...");
                    continue;
                }
                if (records.isEmpty()){
                    System.out.println("开始考试[" + homework.getTitle() + "]..." + (k + 1) + "/" + homeworks.size());
                    platform.doExamination(homework, platform.getExaminationUrl(homework, false).getMsg(), null);
                }else{
                    System.out.println("继续考试[" + homework.getTitle() + "]..." + (k + 1) + "/" + homeworks.size());
                    platform.doExamination(homework, records.get(0).getResumeUrl(), records.get(0).getViewAnswerUrl());
                }
            }
        }
    }

    private static void printScore(Platform platform, Course course) {
        System.out.println("读取学科学分详情[" + course.getName() + "]...");
        var score = platform.getScoreDetail(course);
        System.out.print("学分组成：");
        System.out.println(score.getScoreProportions().stream().map(s->s.getName() + "[" + s.getProportion() + "]").collect(Collectors.joining("\t+\t")));
        System.out.println("课件分数：" + score.getCourseWareLearnScore() + "\t课件学习时间：" + score.getCourseWareLearnTime());
        System.out.println("作业分数：" + score.getHomeworkScores().stream().mapToDouble(HomeworkScore::getScore).sum());
        for (HomeworkScore homeworkScore : score.getHomeworkScores()) {
            System.out.println(homeworkScore.getName() + "\t考试次数：" + homeworkScore.getTimes() + "\t分数：" + homeworkScore.getScore() + "\t总分：" + homeworkScore.getFullMark());
        }
        System.out.println("平时分数：" + score.getUsualPerformance());
        System.out.println("期末考试分数：" + score.getExamResult());
        System.out.println("总分：" + score.getTotalScore());
    }

    @NotNull
    private static List<Course> refreshInformation(DefaultPlatform platform) {
        System.out.println("获取学生信息...");
        var studentInfo = platform.getStudentInfo();
        System.out.println("获取专业...");
        var majors = platform.getMajors();
        var personId = studentInfo.getPersonId();
        System.out.println("姓名: " + studentInfo.getName() + "\t人员ID：" + personId.substring(0, 3) + "************" + personId.substring(personId.length() - 3));
        var major = majors.get(0);
        platform.changeMajor(major);
        System.out.println("专业: " + major.getMajorLongName() + "\t入学年份：" + major.getPlanYear() + "\t学年：" + major.getStudyYearReal() + "\t" + major.getEducationName());
        var classInfo = platform.getClassInfo();
        System.out.println("班级：" + classInfo.getMsg());
        var msg = platform.getMessage();
        if (msg.getMessageContent() != null && !msg.getMessageContent().isBlank()){
            System.out.println("===============消息===============");
            System.out.println("标题：" + msg.getMessageTitle());
            System.out.println(msg.getMessageContent());
            System.out.println("=================================");
        }
        var coursesGroup = platform.getCourses();
        var courses = new ArrayList<Course>();
        System.out.println("学科：");
        var index = 0;
        for (Map.Entry<String, List<Course>> entry : coursesGroup.entrySet()) {
            System.out.println(entry.getKey() + ": ");
            var cs = entry.getValue();
            for (Course course : cs) {
                courses.add(course);
                System.out.println("\t" + (++index) + ". " + course.getName() + "\t得分：" + course.getScore() + "/" + course.getTotalScore());
            }
        }
        return courses;
    }

    private static void watchVideo(DefaultPlatform platform, List<Course> courses, int startIndex) {
        var msg = "";
        loopCourses:
        for (int courseIndex = 0; courseIndex < courses.size(); courseIndex++) {
            Course course = courses.get(courseIndex);
            var courseUrl = platform.getCourseUrl(course).getMsg();
            var businessLineCode = StringUtils.cut(courseUrl, "businessLineCode=", "&");
            var coursewareCode = StringUtils.cut(courseUrl, "coursewareCode=", "&");
            var courseCodeN = StringUtils.cut(courseUrl, "courseCodeN=", "&");
            var catalogId = StringUtils.cut(courseUrl, "catalogId=", "&");
            var clientCode = StringUtils.cut(courseUrl, "clientCode=", "&");
            var userId = StringUtils.cut(courseUrl, "userId=", "&");
            var userName = StringUtils.cut(courseUrl, "userName=", "&");
            var lastTime = StringUtils.cut(courseUrl, "lastTime=", "&");
            var timestamp = StringUtils.cut(courseUrl, "timestamp=", "&");
            var clientKey = StringUtils.cut(courseUrl, "clientKey=", "&");
            var publicKey = StringUtils.cut(courseUrl, "publicKey=", "&");
            var isQuestion = StringUtils.cut(courseUrl, "isQuestion=", "&");
            var drag = StringUtils.cut(courseUrl, "drag=", "&");
            var courseName = StringUtils.cut(courseUrl, "courseName=", "&");
            var hintPoint = StringUtils.cut(courseUrl, "hintPoint=", "&");
            var remark = StringUtils.cut(courseUrl, "remark=", "&");

            System.out.println("解析视频地址[" + course.getName() + "]...");
            var courseWare = platform.findCourseWareByByCode(course, userId, clientCode);
            System.out.println("读取视频目录[" + courseWare.getName() + "]...");
            var courseCatalogs = platform.getCourseCatalog(courseWare, userId, clientCode).stream().filter(CourseWareCatalog::getIsMedia).toList();
            for (int i = courseIndex == 0 ? Math.max(startIndex, 0) : 0; i < courseCatalogs.size(); i++) {
                CourseWareCatalog courseCatalog = courseCatalogs.get(i);
                if (startIndex < 0 && (courseCatalog.getMediaDuration() - courseCatalog.getValidTime() < 30 || courseCatalog.getMediaDuration() - courseCatalog.getAccumulativeTimeLast() < 30))
                    continue;
                var catalogInfo = platform.getCourseCatalogInfo(courseCatalog, userId, clientCode);
                var learnRecord = platform.getUserLearnRecord(userId, userName, clientCode, courseWare, catalogInfo);
                if (learnRecord == null) {
                    learnRecord = new LearnRecord();
                    learnRecord.setAccumulativeTime(0);
                    learnRecord.setUserId(userId);
                    learnRecord.setUserName(userName);
                    learnRecord.setClientCode(clientCode);
                    learnRecord.setBusinessLineCode(businessLineCode);
                    learnRecord.setCoursewareCode(coursewareCode);
                    learnRecord.setCoursewareName(courseWare.getCourseName());
                    learnRecord.setCatalogId(catalogInfo.getId());
                    learnRecord.setCatalogTitle(catalogInfo.getTitle());
                    learnRecord.setBackUrl(String.format(Platform.URL_LEARN_RETURN_URL_INDEX, userName));
                    learnRecord.setValidTime(0);
                    learnRecord.setLastTime(0);
                    learnRecord.setAccumulativeTime(0);
                    learnRecord.setToken("");
                    learnRecord = platform.postLearnRecord(learnRecord);
                }
                if (startIndex >= 0){
                    learnRecord.setAccumulativeTime(0);
                    learnRecord.setLastTime(0);
                }
                for (
                        ;
                        learnRecord.getLastTime() < catalogInfo.getMediaDuration();
                        learnRecord.setLastTime(Math.min(learnRecord.getLastTime() + 60, catalogInfo.getMediaDuration())), learnRecord.setAccumulativeTime(learnRecord.getAccumulativeTime() + 60)
                ) {
                    if (learnRecord.getLastTime() > 0) {
                        submitAndSyncLearnRecord(platform, userName, learnRecord);
                    }
                    try {
                        var waitSeconds = Math.min(60, catalogInfo.getMediaDuration() - learnRecord.getLastTime());
                        for (int j = 0; j < waitSeconds; j++) {
                            var remaining = catalogInfo.getMediaDuration() - learnRecord.getLastTime() - j;
                            var prevLen = msg.length();
                            msg = "\rwatch video [" + courseWare.getName() + "]\t" + courseCatalog.getName() + "\t" + (i + 1) + "/" + courseCatalogs.size() +
                                    "\t进度：" + (learnRecord.getLastTime() + j) / 60 + ":" + (learnRecord.getLastTime() + j) % 60 +
                                    "\t时长: " + catalogInfo.getMediaDuration() / 60 + ":" + catalogInfo.getMediaDuration() % 60 +
                                    "\t剩余: " + remaining / 60 + ":" + remaining % 60;
                            System.out.print(msg + " ".repeat(Math.max(0, prevLen - msg.length() + 1)));
                            Thread.sleep(1000);
                            if (System.in.available() > 0) {
                                if ((char) System.in.read() == 'q') {
                                    break loopCourses;
                                }
                            }
                        }
                    } catch (InterruptedException | IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                submitAndSyncLearnRecord(platform, userName, learnRecord);
            }
        }
    }

    private static void submitAndSyncLearnRecord(DefaultPlatform platform, String userName, LearnRecord learnRecord) {
        var retryTimes = 5;
        var times = retryTimes;
        while (times-- > 0){
            try {
                platform.syncLearnRecord(userName, platform.putLearnRecord(learnRecord));
                break;
            }catch (Throwable e){
                if (times < 0){
                    throw new PlatformException("提交学习记录失败,重试超过" + retryTimes + "次", e);
                }else{
                    System.out.println("错误：" + e.getMessage());
                    System.out.println(((int) Math.pow(retryTimes - times, 4)) + "秒后重试...");
                    try {
                        Thread.sleep((long) (Math.pow(retryTimes - times, 4) * 1000));
                    } catch (InterruptedException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }
    }

    private static void printUsage() {
        System.out.println("版本：" + readVersion());
        System.out.println("使用方法：");
        System.out.println("watch-video/wv [学科序号] [目录序号]: 从指定学科开始自动观看视频，若不给定学科，将自动观看所有视频.若给定目录序号将无视记录进度，从指定序号开始刷.");
        System.out.println("homework [学科序号] [作业序号]: 从指定学科开始自动做题，若不给定作业序号，将做所有该学科未做的题.");
        System.out.println("score [学科序号] : 获取和打印学科学分，该方法能刷新分数.");
        System.out.println("refresh : 刷新并打印用户信息.");
        System.out.println("help : 使用帮助.");
        System.out.println("exit/quit : 退出.");
    }

    private static String readVersion(){
        try {
            Class<?> clazz = Main.class;
            String className = clazz.getSimpleName() + ".class";
            String classPath = Objects.requireNonNull(clazz.getResource(className)).toString();

            if (classPath.startsWith("jar:file:")) {
                // Running from a JAR file
                String jarPath = classPath.substring(10, classPath.indexOf("!"));
                try(var jar = new JarFile(jarPath)){
                    Manifest manifest = jar.getManifest();
                    Attributes attributes = manifest.getMainAttributes();
                    return attributes.getValue("Implementation-Version");
                }
            } else {
                return "";
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static Account readAccount(Scanner scanner){
        var accountFile = new File(".account");
        var account = new Account();
        account.setUsername(System.getenv("CS_USERNAME"));
        account.setPassword(System.getenv("CS_PASSWORD"));
        if (account.getUsername() != null && !account.getUsername().isBlank()){
            if (account.getPassword() == null || account.getPassword().isBlank()){
                account.setPassword(account.getUsername());
            }
            return account;
        }else if (accountFile.exists()){
            try(var in = new FileInputStream(accountFile)){
                var accountLine = new String(in.readAllBytes()).split(",");
                account.setUsername(accountLine[0]);
                account.setPassword(accountLine[1]);
            } catch (IOException e) {
                if(!accountFile.delete())
                    System.out.println("删除账号文件失败，请手动删除：" + accountFile.getAbsolutePath());
                System.out.println("读取账号密码失败.");
                throw new RuntimeException(e);
            }
        }else{
            System.out.print("请输入你的账号: ");
            account.setUsername(scanner.next().trim());
            System.out.print("请输入你的密码: ");
            account.setPassword(scanner.next().trim());
            try(var out = new FileOutputStream(accountFile)){
                out.write((account.getUsername() + "," + account.getPassword()).getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return account;
    }

}
